package pers.cfeng.server.versionManage;

import lombok.Getter;
import pers.cfeng.common.AbstractDataCache;
import pers.cfeng.common.DatabaseException;
import pers.cfeng.common.ErrorCode;
import pers.cfeng.server.dataManage.DataManager;
import pers.cfeng.server.txManage.TransactionConstant;
import pers.cfeng.server.txManage.TransactionManager;
import pers.cfeng.server.utils.FaultHandler;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 版本管理主要就是下层的事务管理和数据管理完成数据的操作， 因为会保证最低的隔离级别
 * 直接操作的对象就是Entry， 所以需要对Entry进行缓存管理
 * 就像底层的DataItem和Page
 */

@Getter
public class VersionManagerImpl extends AbstractDataCache<Entry> implements VersionManager {

    private TransactionManager transactionManager;
    private DataManager dataManager;
    private Map<Long, Transaction> runningTransaction; //当前未提交的事务
    private Lock lock;
    private LockTable lockTable;

    public VersionManagerImpl(TransactionManager tm, DataManager dm) {
        super(0);
        this.transactionManager = tm;
        this.dataManager = dm;
        this.runningTransaction = new HashMap<>();
        runningTransaction.put(TransactionConstant.NONE_TID, Transaction.newTransaction(TransactionConstant.NONE_TID, 0, null));
        this.lock = new ReentrantLock();
        this.lockTable = new LockTable();
    }

    //数据导入内存
    @Override
    protected Entry getToCache(long key) throws Exception {
        Entry entry = Entry.loadEntry(key, this);
        if(entry == null)
            throw new DatabaseException(ErrorCode.NULL_ENTRY);
        return entry;
    }


    public void releaseEntry(Entry entry) {
        super.release(entry.getUid());
    }

    @Override
    protected void releaseForCache(Entry obj) {
        obj.remove();
    }

    //加锁读取
    @Override
    public byte[] read(long tid, long uid) throws Exception {
        lock.lock();
        Transaction t = runningTransaction.get(tid);
        lock.unlock();
        if(t.getErr() != null)
            throw t.getErr();
        Entry entry = null;
        try {
            entry = super.get(uid);
        } catch (Exception e) {
            if(e.equals(new DatabaseException(ErrorCode.NULL_ENTRY))) {
                return null;
            } else
                throw e;
        }

        try {
            if(Visibility.isVisible(transactionManager, t, entry)) {
                return entry.data();
            } else {
                return null;
            }
        } finally {
            entry.release();
        }
    }

    @Override
    public long insert(long tid, byte[] data) throws Exception {
        lock.lock();
        Transaction t = runningTransaction.get(tid);
        lock.unlock();

        if(t.getErr() != null)
            throw t.getErr();

        byte[] raw = Entry.wrapEntryRaw(tid, data);
        //dm插入DataItem
        return dataManager.insert(tid, raw);
    }

    @Override
    public boolean delete(long tid, long uid) throws Exception {
        lock.lock();
        Transaction t = runningTransaction.get(tid);
        lock.unlock();

        if(t.getErr() != null)
            throw t.getErr();

        Entry entry = null;
        try {
            entry = super.get(uid);
        } catch (Exception e) {
            if(e.equals(new DatabaseException(ErrorCode.NULL_ENTRY))) {
                return false;
            } else
                throw e;
        }

        try {
            if(!Visibility.isVisible(transactionManager, t, entry))
                return false;
            Lock l = null;
            try {
                l = lockTable.add(tid, uid);
            } catch (Exception e) {
                t.setErr(new DatabaseException(ErrorCode.CONCURRENT_UPDATE));
                internRollback(tid, true); //撤销回滚
                t.setAutoRollback(true);
                throw t.getErr();
            }
            if(l != null) {
                l.lock();
                l.unlock();
            }
            if(Visibility.isVersionSkip(transactionManager, t, entry)) {
                t.setErr(new DatabaseException(ErrorCode.CONCURRENT_UPDATE));
                internRollback(tid, true); //撤销
                t.setAutoRollback(true);
                throw t.getErr();
            }
            //删除就是将Tmax设置为当前的tid
            entry.setTmax(tid);
            return true;
        } finally {
            entry.release();
        }
    }

    /**
     * 开启一个事务， 需要设置隔离级别，利用TM的begin
     */
    @Override
    public long begin(int level) {
        lock.lock();
        try {
            long tid = transactionManager.begin();
            Transaction t = Transaction.newTransaction(tid, level, runningTransaction);
            runningTransaction.put(tid, t);
            return tid;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void commit(long tid) throws Exception {
        lock.lock();
        Transaction t = runningTransaction.get(tid);
        lock.unlock();

        try {
            if(t.getErr() != null)
                throw t.getErr();
        } catch (NullPointerException e) {
            System.out.println(tid + " 出错啦 "+ runningTransaction.keySet());
            FaultHandler.forcedStop(e);
        }

        lock.lock();
        runningTransaction.remove(tid); //从当前运行的事务中移除
        lock.unlock();
        //资源表中移除当前的tid
        lockTable.remove(tid);
        transactionManager.commit(tid); //提交事务
    }

    @Override
    public void rollback(long tid) {
        internRollback(tid, false); //手动回滚
    }

    private void internRollback(long tid, boolean autoRollback) {
        lock.lock();
        Transaction t  = runningTransaction.get(tid);
        if(!autoRollback) {
            runningTransaction.remove(tid);
        }
        lock.unlock();

        if(t.isAutoRollback())
            return;
        lockTable.remove(tid);
        transactionManager.rollback(tid);
    }
}
